package com.licode.codekit.utils;

import com.licode.codekit.buildprj.CodeKitConf;
import com.licode.codekit.modal.ModuleItem;
import org.junit.Test;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <author>licode</author>
 *     <module-type>simple-parent</module-type>
 *     <modules>
 *         <module name="{module-name}-client">
 *             {package}.client
 *         </module>
 *         <module name="{module-name}-adapter">
 *             {package}.adapter
 *         </module>
 *     </modules>
 *     <create-time></create-time>
 *     <version>{version}</version>
 *     <db-setting>
 *         <dbName>usr_znms</dbName>
 *         <dbHost>10.0.14.62</dbHost>
 *         <dbPort>3306</dbPort>
 *         <dbUser>apartment</dbUser>
 *         <dbPwd>sRbzL1rO3OEHqS^I</dbPwd>
 *         <dbUri>jdbc:mysql://10.0.14.62:3306/usr_znms?serverTimezone=Asia/Shanghai</dbUri>
 *         <dbDriver>F:\data\mysql-connector-java-8.0.19.jar</dbDriver>
 *         <tablePrefix>2</tablePrefix>
 *         <dbUrlTemplate>jdbc:mysql://{host}:{port}/{name}?serverTimezone=Asia/Shanghai</dbUrlTemplate>
 *     </db-setting>
 */
public class LiConfig {
    private static final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    private String filePath;
    private Document rootDoc;

    private Node dbSetting;
    private Node modulesNode;

    @Test
    public void test() {
        LiConfig pomFile = new LiConfig();
        pomFile.parse("d:\\pom.xml");
        pomFile.save("d:\\pom-1.xml");
    }

    public boolean isValid() {
        return null != rootDoc;
    }

    public void createConfig(String filePath) {
        if (null == rootDoc) {
            Element root;
            DocumentBuilder docBuilder;
            String strDir = StrUtil.getFileDir(filePath);

            try {
                docBuilder = dbf.newDocumentBuilder();
                new File(strDir).mkdirs();
                rootDoc = docBuilder.newDocument();
                root = rootDoc.createElement(CodeKitConf.LI_CONFIG);
                rootDoc.appendChild(root);
                this.filePath = filePath;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public boolean parse(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            this.filePath = filePath;
            parse(fis);
        } catch (Exception e) {
            e.printStackTrace();
            rootDoc = null;
            dbSetting = null;
        }
        return isValid();
    }

    public boolean parse(InputStream inputStream) {
        try {
            rootDoc = dbf.newDocumentBuilder().parse(inputStream);
            this.initData();
        } catch (Exception e) {
            e.printStackTrace();
            rootDoc = null;
            dbSetting = null;
        }
        return isValid();
    }

    private Element appendNode(Node root, String name) {
        Element element = rootDoc.createElement(name);
        root.appendChild(element);
        return element;
    }

    private Element appendNode(Node root, String name, String val) {
        Node parent = root;
        Element element = null;

        if (name.startsWith(CodeKitConf.DB_PREFIX)) {
            parent = checkDbSetting();
        }

        if (null != parent) {
            element = rootDoc.createElement(name);
            element.setTextContent(val);
            parent.appendChild(element);
        }

        return element;
    }

    private void removeBlankNodes() {
        XmlUtil.removeBlankNodes(rootDoc.getDocumentElement());
    }

    public void setProperty(String name, String val) {
        updateOrInsertNodeValue(getRootNode(), name, val);
    }

    public String getProperty(String name, String defVal) {
        return XmlUtil.getNodeValue(getRootNode(), name, defVal);
    }

    public void setDbProperty(String name, String val) {
        updateOrInsertNodeValue(dbSetting, name, val);
    }

    public String getDbProperty(String name, String defVal) {
        return XmlUtil.getNodeValue(dbSetting, name, defVal);
    }

    public String getModuleType() {
        return getProperty(CodeKitConf.LI_MODULE_TYPE, "");
    }

    public String getPackage() {
        return getProperty(CodeKitConf.LI_PACKAGE, "");
    }

    public String getClientPackage() {
        return getProperty(CodeKitConf.LI_CLIENT_PACKAGE, "");
    }

    public String getServicePackage() {
        return getProperty(CodeKitConf.LI_SERVICE_PACKAGE, "");
    }

    public String getClientModule() {
        return getProperty(CodeKitConf.LI_CLIENT_MODULE, "");
    }

    public String getServiceModule() {
        return getProperty(CodeKitConf.LI_SERVICE_MODULE, "");
    }

    public String getDbUser() {
        return getDbProperty(CodeKitConf.DB_USER, "root");
    }

    public void setDbUser(String dbUser) {
        this.setDbProperty(CodeKitConf.DB_USER, dbUser);
    }

    public String getDbName() {
        return getDbProperty(CodeKitConf.DB_NAME, "xxx");
    }

    public void setDbName(String dbName) {
        this.setDbProperty(CodeKitConf.DB_NAME, dbName);
    }

    public String getDbHost() {
        return getDbProperty(CodeKitConf.DB_HOST, "localhost");
    }

    public void setDbHost(String dbHost) {
        this.setDbProperty(CodeKitConf.DB_HOST, dbHost);
    }

    public String getDbPort() {
        return getDbProperty(CodeKitConf.DB_PORT, "3306");
    }

    public void setDbPort(String dbPort) {
        this.setDbProperty(CodeKitConf.DB_PORT, dbPort);
    }

    public String getDbPwd() {
        return getDbProperty(CodeKitConf.DB_PWD, "123456");
    }

    public void setDbPwd(String dbPwd) {
        this.setDbProperty(CodeKitConf.DB_PWD, dbPwd);
    }

    public String getDbUri() {
        return getDbProperty(CodeKitConf.DB_URI, "");
    }

    public void setDbUri(String dbUri) {
        this.setDbProperty(CodeKitConf.DB_URI, dbUri);
    }

    public String getAuthor() {
        return getProperty(CodeKitConf.LI_AUTHOR, "licode");
    }

    public void setAuthor(String author) {
        this.setProperty(CodeKitConf.LI_AUTHOR, author);
    }

    public int getTablePrefix() {
        return Integer.parseInt(getDbProperty(CodeKitConf.DB_TABLE_PREFIX, "0"));
    }

    public void setTablePrefix(int tablePrefix) {
        this.setDbProperty(CodeKitConf.DB_TABLE_PREFIX, String.valueOf(tablePrefix));
    }

    public String getDbUrlTemplate() {
        return getDbProperty(CodeKitConf.DB_URI_TEMPLATE, "jdbc:mysql://{host}:{port}/{name}?serverTimezone=Asia/Shanghai");
    }

    public void setDbUrlTemplate(String dbUrlTemplate) {
        this.setDbProperty(CodeKitConf.DB_URI_TEMPLATE, dbUrlTemplate);
    }

    public Map<String, String> getModules() {
        Map<String, String>  modules = new HashMap<>();
        if (null != modulesNode) {
            NodeList nodeList = modulesNode.getChildNodes();
            for (int i = 0; i < nodeList.getLength(); ++i) {
                Node node = nodeList.item(i);
                if (node instanceof Element && node.getNodeName().equals("module")) {
                    Element elem = (Element)node;
                    String name = elem.getAttribute("name");
                    modules.put(name, elem.getTextContent());
                }
            }
        }
        return modules;
    }

    private void initData() {
        Element root = rootDoc.getDocumentElement();
        NodeList nodeList = root.getChildNodes();

        for (int i = 0; i < nodeList.getLength(); ++i) {
            Node node = nodeList.item(i);
            if (node.getNodeName().equals(CodeKitConf.DB_SETTING)) {
                this.dbSetting = node;
            } else if (node.getNodeName().equals(CodeKitConf.LI_MODULES)) {
                this.modulesNode = node;
            }
        }
    }

    private Node checkDbSetting() {
        if (null == dbSetting) {
            dbSetting = appendNode(getRootNode(), CodeKitConf.DB_SETTING);
        }
        return dbSetting;
    }

    private void updateOrInsertNodeValue(Node root, String key, String val) {
        if (null != rootDoc) {
            if (!XmlUtil.updateNodeTextValue(root, key, val)) {
                appendNode(root, key, val);
            }
        }
    }

    private Node getRootNode() {
        return rootDoc == null ? null : rootDoc.getDocumentElement();
    }

    public void save() {
        this.save(this.filePath);
        this.parse(filePath);
    }

    public void save(String filePath) {
        DOMSource domSource;
        TransformerFactory transFactory = TransformerFactory.newInstance();

        try {
            Transformer transFormer = transFactory.newTransformer();
            // 美化缩进
            transFormer.setOutputProperty(OutputKeys.INDENT, "yes");

            this.removeBlankNodes();

            // 设置输出结果
            domSource = new DOMSource(rootDoc);

            // 生成xml文件
            File file = new File(filePath);

            // 推断是否存在,假设不存在,则创建
            if (!file.exists()) {
                file.createNewFile();
            }

            // 文件输出流
            FileOutputStream out1 = new FileOutputStream(file);

            // 设置输入源
            StreamResult xmlResult = new StreamResult(out1);

            // 输出xml文件
            transFormer.transform(domSource, xmlResult);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
